#include <defs.h>
#include <mmu.h>
#include <memlayout.h>
#include <clock.h>
#include <trap.h>
#include <x86.h>
#include <stdio.h>
#include <assert.h>
#include <console.h>
#include <kdebug.h>
#include <string.h>

#define TICK_NUM 100

static void print_ticks() {
    cprintf("%d ticks\n",TICK_NUM);
#ifdef DEBUG_GRADE
    cprintf("End of Test.\n");
    panic("EOT: kernel seems ok.");
#endif
}

/* *
 * Interrupt descriptor table:
 *
 * Must be built at run time because shifted function addresses can't
 * be represented in relocation records.
 * */
static struct gatedesc idt[256] = {{0}};

static struct pseudodesc idt_pd = {
    sizeof(idt) - 1, (uintptr_t)idt
};

/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */
void
idt_init(void) {
     /* LAB1 YOUR CODE : STEP 2 */
     /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)?
      *     All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ?
      *     __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c
      *     (try "make" command in lab1, then you will find vector.S in kern/trap DIR)
      *     You can use  "extern uintptr_t __vectors[];" to define this extern variable which will be used later.
      * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT).
      *     Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT
      * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction.
      *     You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more.
      *     Notice: the argument of lidt is idt_pd. try to find it!
      */
	extern uintptr_t __vectors[];
	int size = sizeof(idt) / sizeof(idt[0]);
	int i;
	for (i = 0; i < size; ++i) {
		SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
	}

	// system call's dpl should be DPL_USER
	SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);

	// 临时(用于从用户态切换为内核态)
	SETGATE(idt[T_SWITCH_TOK], 1, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);

	// set idt
	lidt(&idt_pd);
}

static const char *
trapname(int trapno) {
    static const char * const excnames[] = {
        "Divide error",
        "Debug",
        "Non-Maskable Interrupt",
        "Breakpoint",
        "Overflow",
        "BOUND Range Exceeded",
        "Invalid Opcode",
        "Device Not Available",
        "Double Fault",
        "Coprocessor Segment Overrun",
        "Invalid TSS",
        "Segment Not Present",
        "Stack Fault",
        "General Protection",
        "Page Fault",
        "(unknown trap)",
        "x87 FPU Floating-Point Error",
        "Alignment Check",
        "Machine-Check",
        "SIMD Floating-Point Exception"
    };

    if (trapno < sizeof(excnames)/sizeof(const char * const)) {
        return excnames[trapno];
    }
    if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) {
        return "Hardware Interrupt";
    }
    return "(unknown trap)";
}

/* trap_in_kernel - test if trap happened in kernel */
bool
trap_in_kernel(struct trapframe *tf) {
    return (tf->tf_cs == (uint16_t)KERNEL_CS);
}

static const char *IA32flags[] = {
    "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF",
    "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL,
    "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL,
};

void
print_trapframe(struct trapframe *tf) {
    cprintf("trapframe at %p\n", tf);
    print_regs(&tf->tf_regs);
    cprintf("  ds   0x----%04x\n", tf->tf_ds);
    cprintf("  es   0x----%04x\n", tf->tf_es);
    cprintf("  fs   0x----%04x\n", tf->tf_fs);
    cprintf("  gs   0x----%04x\n", tf->tf_gs);
    cprintf("  trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno));
    cprintf("  err  0x%08x\n", tf->tf_err);
    cprintf("  eip  0x%08x\n", tf->tf_eip);
    cprintf("  cs   0x----%04x\n", tf->tf_cs);
    cprintf("  flag 0x%08x ", tf->tf_eflags);

    int i, j;
    for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) {
        if ((tf->tf_eflags & j) && IA32flags[i] != NULL) {
            cprintf("%s,", IA32flags[i]);
        }
    }
    cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12);

    if (!trap_in_kernel(tf)) {
        cprintf("  esp  0x%08x\n", tf->tf_esp);
        cprintf("  ss   0x----%04x\n", tf->tf_ss);
    }
}

void
print_regs(struct pushregs *regs) {
    cprintf("  edi  0x%08x\n", regs->reg_edi);
    cprintf("  esi  0x%08x\n", regs->reg_esi);
    cprintf("  ebp  0x%08x\n", regs->reg_ebp);
    cprintf("  oesp 0x%08x\n", regs->reg_oesp);
    cprintf("  ebx  0x%08x\n", regs->reg_ebx);
    cprintf("  edx  0x%08x\n", regs->reg_edx);
    cprintf("  ecx  0x%08x\n", regs->reg_ecx);
    cprintf("  eax  0x%08x\n", regs->reg_eax);
}

// 设置临时栈
static struct trapframe tfu, *tfptr;

/* trap_dispatch - dispatch based on what type of trap occurred */
static void
trap_dispatch(struct trapframe *tf) {
    char c;
    switch (tf->tf_trapno) {
    case IRQ_OFFSET + IRQ_TIMER:
        /* LAB1 YOUR CODE : STEP 3 */
        /* handle the timer interrupt */
        /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c
         * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks().
         * (3) Too Simple? Yes, I think so!
         */
    		++ticks;
    		// call print_ticks per TICK_NUM ticks
    		if (ticks % TICK_NUM == 0) {
			print_ticks();
		}
        break;
    case IRQ_OFFSET + IRQ_COM1:
        c = cons_getc();
        cprintf("serial [%03d] %c\n", c, c);
        break;
    case IRQ_OFFSET + IRQ_KBD:
        c = cons_getc();
        cprintf("kbd [%03d] %c\n", c, c);
        if (c == 0x30) {
        		cprintf("nermber 0 pressed.\n");
        		if (tf->tf_cs != KERNEL_CS) {
        			cprintf("switch to kernel mode.\n");
				tf->tf_cs = KERNEL_CS;
				tf->tf_ds = KERNEL_DS;
				tf->tf_es = KERNEL_DS;
				tf->tf_ss = KERNEL_DS;

				tf->tf_eflags &= ~(FL_IOPL_3);

				tfptr = (struct trapframe *)(tf->tf_esp - offsetof(struct trapframe, tf_esp));
				memmove(tfptr, tf, sizeof(struct trapframe));
				*((uint32_t *)tf - 1) = (uint32_t)tfptr;
			} else {
				cprintf("already in kernel mode.\n");
			}
		} else if (c == 0x33) {
			cprintf("nermber 3 pressed.\n");
			if (tf->tf_cs != USER_CS) {
				cprintf("switch to user mode.\n");
				tfu = *tf;
				tfptr = &tfu;

				tfptr->tf_cs = USER_CS;
				tfptr->tf_ds = USER_DS;
				tfptr->tf_es = USER_DS;
				tfptr->tf_ss = USER_DS;
				tfptr->tf_esp = (uint32_t)tf + offsetof(struct trapframe, tf_esp);

				tfptr->tf_eflags |= FL_IOPL_3;

				*((uint32_t *)tf - 1) = (uint32_t)tfptr;
			} else {
				cprintf("already in user mode.\n");
			}
		}
        break;
    //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes.
    case T_SWITCH_TOU:
    		// CS寄存器不为用户代码段
    		if (tf->tf_cs != USER_CS) {
    			// init.c中lab1_switch_to_user调用int 0x78后形成如下栈帧
    			/* |   SS   | */
    			/* |   ESP  | */ // <- 需要将临时栈的ESP值改为指向这里，以便后面恢复
    			/* | EFLAGS | */
    			/* |   CS   | */
    			/* |   EIP  | */
    			/* |  ERRNO | */ // vectors.S: pushl $0
    			/* | TRAPNO | */ // vectors.S: pushl $120
    			/* |   DS   | */ // trapentry.S: pushl %dx
    			/* |   ES   | */ // trapentry.S: pushl %es
    			/* |   FS   | */ // trapentry.S: pushl %fs
    			/* |   GS   | */ // trapentry.S: pushl %gs
    			/* |   EAX  | */ // trapentry.S: pushal
    			/* |   ECX  | */ // trapentry.S: pushal
    			/* |   EDX  | */ // trapentry.S: pushal
    			/* |   EBX  | */ // trapentry.S: pushal
    			/* |  OESP  | */ // trapentry.S: pushal
    			/* |   EBP  | */ // trapentry.S: pushal
    			/* |   ESI  | */ // trapentry.S: pushal
    			/* |   EDI  | */ // trapentry.S: pushal
    			/* |   TF   | */ // 0x7b2c - 0x7b28 trapentry.S: pushl %esp 前面的ESP值，指向当前位置的起始地址，即trap参数
    			/* |   EIP  | */ // 返回地址(下一条指令)，由call设定
    			/* |        | */ // ESP指向这里
    			// 先拷贝内核态栈到临时栈
    			tfu = *tf;
    			tfptr = &tfu;

    			// 设置代码段、数据段、栈段为相应的用户态段
    			tfptr->tf_cs = USER_CS;
    			tfptr->tf_ds = USER_DS;
    			tfptr->tf_es = USER_DS;
    			tfptr->tf_ss = USER_DS;

    			// 设置当前的栈顶
    			tfptr->tf_esp = (uint32_t)tf + offsetof(struct trapframe, tf_esp);

    			// 使用户态能够访问IO(用来表示用户态)，如果不切换的话由于新栈的CPL=3, > IOPL，内核会产生保护异常
    			tfptr->tf_eflags |= FL_IOPL_3;

    			// 使用临时栈覆盖原来的栈(存放为新的临时栈的栈帧地址)
    			*((uint32_t *)tf - 1) = (uint32_t)tfptr;
		}
    		break;
    case T_SWITCH_TOK:
    		if (tf->tf_cs != KERNEL_CS) {
			// 修改临时栈、使之变为具有内核栈的属性
			tf->tf_cs = KERNEL_CS;
			tf->tf_ds = KERNEL_DS;
			tf->tf_es = KERNEL_DS;
			tf->tf_ss = KERNEL_DS;

			// 将IOCP恢复(虽然不恢复也能切换会内核模式，但是不严谨)
			tf->tf_eflags &= ~(FL_IOPL_3);

			// 恢复原来的内核栈顶位置，并将当前的栈的内容恢复到原来的内核栈(为何不直接使用原来的内核栈？有可能
			// 在用户态的操作中改变了EAX等寄存器!)
			tfptr = (struct trapframe *)(tf->tf_esp - offsetof(struct trapframe, tf_esp));
			memmove(tfptr, tf, sizeof(struct trapframe));
			*((uint32_t *)tf - 1) = (uint32_t)tfptr;
		}
        break;
    case IRQ_OFFSET + IRQ_IDE1:
    case IRQ_OFFSET + IRQ_IDE2:
        /* do nothing */
        break;
    default:
        // in kernel, it must be a mistake
        if ((tf->tf_cs & 3) == 0) {
            print_trapframe(tf);
            panic("unexpected trap in kernel.\n");
        }
    }
}

/* *
 * trap - handles or dispatches an exception/interrupt. if and when trap() returns,
 * the code in kern/trap/trapentry.S restores the old CPU state saved in the
 * trapframe and then uses the iret instruction to return from the exception.
 * */
void
trap(struct trapframe *tf) {
    // dispatch based on what type of trap occurred
    trap_dispatch(tf);
}

